﻿using System;
using System.Linq;
using System.Threading.Tasks;

namespace client35 {
	using Autofac;
	using client35.Properties;
	using common;
	using i_core;
	using i_core.usr;
	using lmsft;
	using lmsft.Media;
	using lmsft.SIP.Stack;
	using lmsft_sip;
	using lmsft_sip.sip;
	using System.Net;
	using System.Net.Sockets;
	using System.Threading;
	public class sip_reg {
		public static SIP_Stack_Extension sip_stack;
		public static i_log log;

		public static async Task<bool> run(i_account acc, CancellationToken cancel_token) {
			var task_source = new TaskCompletionSource<bool>();
			var cancel_source = new CancellationTokenSource(5000);
			cancel_token.Register(
				() => {
					if (!cancel_source.IsCancellationRequested) {
						cancel_source.Cancel();
						cancel_source.Dispose();
					}
					task_source.TrySetCanceled();
				});

			cancel_source.Token.Register(
				() => {
					if (log != null)
						log.Warn($"sip (name={acc.display_name}) register failed,cause by time out (time=5000)");

					task_source.TrySetResult(false);
				});

			try {
				log = global_vars.scope.ResolveKeyed<i_log>(log_type.log4net,
						new NamedParameter("user", "SIP"),
						new NamedParameter("islog", true)
				);

				if (acc == null)
					throw new Exception("account is null");

				sip_param.m_pPlayer = new WavePlayer(sip_param.m_pAudioOutDevice);

				var sip_acc = acc as sip_account;

				sip_acc.local_adr = Settings.Default.current_ip;
				sip_acc.rtp_ip = Settings.Default.current_ip;

				log.Debug("initilizate sip stack");

				//force use the random ports
				TcpListener _listen = new TcpListener(IPAddress.Loopback, 0);
				_listen.Start();
				var temp_port = sip_acc.local_port = ((IPEndPoint)_listen.LocalEndpoint).Port;
				_listen.Stop();

				sip_stack = new SIP_Stack_Extension() {
					UserAgent = "ceno_robot 2.3",
					BindInfo = new IPBindInfo[] { new IPBindInfo("", BindInfoProtocol.UDP, IPAddress.Parse(sip_acc.local_adr), temp_port) },
					rtp_ip = sip_acc.rtp_ip,
					rtp_port = sip_acc.rtp_port,
					rtp_codec = sip_acc.rtp_codec
				};
				sip_stack.Credentials.Add(new NetworkCredential(sip_acc.name, sip_acc.password, sip_acc.reg_ip));

				var s_stack_req = new sip_stack_req();
				s_stack_req.log = log;
				sip_stack.Error += s_stack_req.stack_error;
				sip_stack.RequestReceived += s_stack_req.stack_request_received;
				sip_stack.ResponseReceived += s_stack_req.stack_response_received;
				//sip_stack.ValidateRequest += s_stack_req.stack_validate_request;
				sip_stack.Start();

				var url = AbsoluteUri.Parse($"sip:{ sip_acc.name }@{ sip_acc.local_adr }:{ temp_port}");
				var serveruri = new SIP_Uri() {
					Host = sip_acc.reg_ip,
					Port = sip_acc.reg_port,
					User = sip_acc.name
				};
				var aro = sip_acc.name + "@" + serveruri.Host;
				var Registration = sip_stack.CreateRegistration(serveruri, aro, url, sip_acc.express);

				log.Debug("start register account to server");
				Registration.BeginRegister(true);

				Registration.StateChanged += new EventHandler(
					(obj, ev) => {
						switch (Registration.State) {
							case SIP_UA_RegistrationState.Registered:
								if (!cancel_source.IsCancellationRequested) {
									cancel_source.Dispose();
									task_source.TrySetResult(true);
								}
								break;
							case SIP_UA_RegistrationState.Unregistered:
								log.Warn($"sip account {sip_acc.display_name} has unregistered");
								if (!cancel_source.IsCancellationRequested) {
									cancel_source.Dispose();
									task_source.TrySetResult(false);
								}
								break;
						}
					});
			} catch (Exception ex) {
				log.Error("run ready error", ex);
				task_source.SetException(ex);
			}
			return await task_source.Task;
		}

		public static async Task<bool> end() {
			return await Task.Factory.StartNew(() => {
				var end_rst = false;
				try {
					if (sip_stack == null)
						throw new ArgumentNullException("sip_stack");
					if (sip_stack.Registrations.Count() <= 0)
						throw new ArgumentException("Registrations");
					foreach (var reg in sip_stack.Registrations) {
						reg.BeginUnregister(true);
					}
					sip_stack.Stop();
					sip_stack.Dispose();
					sip_stack = null;
					end_rst = true;
				} catch (Exception ex) {
					end_rst = false;
					log.Warn("unregister sip error.", ex);
				}
				return end_rst;
			});
		}
	}
}
